"""
This module contains clsses that execute and display the progress
of a Landis-II application.
    - ProcessInterface: Specification for the process class
    - Process: Executes the L-II application
    - _ProgressDialog: Displays the progress of the running L-II application
"""
"""
   Copyright 2011 Gabriel Elkind and Brian Gapinski

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
"""
import os
import logging
import re

import wx

###
#The process appears to be causing the command line to hang after lime
#has been quit. Probably need to send another kill command or some such
###
logger = logging.getLogger('process')
class ProcessInterface(object):
    """
    This class is used to execute a Landis-II process and
    redirect the output stream.
    """
    def __init__(self, cmd, output, step):
        """
        Constructs the process

        @type cmd: string
        @param cmd: The command the process is to run

        @type output: Display
        @param output: The display that is to receive the output stream

        @type step: int
        @param step: The interval size for each progress update
        """
        pass

    def Start(self):
        """
        Tells the process to start its execution
        """
        raise NotImplementedError

    def Terminate(self):
        """
        Ends the process prematurely
        """
        raise NotImplementedError


class Process(wx.Process, ProcessInterface):
    """
    This class is used to execute a Landis-II process. After a set
    time interval, the buffer of the application is outputted and
    redirected so the user can view this text as it appears.

    @type _cmd: string
    @ivar _cmd: The command the process is to execute

    @type _output: Text window
    @ivar _output: The window that is to receive and display the output text

    @type _pid: int
    @ivar _pid: The process id of the current process

    @type _progress: ProgressDialog
    @ivar _progress: Displays the current progress of the process

    @type _count: int
    @ivar _count: This is the numerical value of the progress

    @type _step: int
    @ivar _step: The amount the progress increases at each interval

    @type _iscanceled: bool
    @ivar _iscanceled: True if the user cancels the process, false otherwise

    @type _has_error: bool
    @ivar _has_error: True if the process encounters an error, false otherwise

    @type _timer: Timer
    @ivar _timer: A timer to handle buffer dump events
    """
    def __init__(self, cmd, step, notify=None):
        """
        This class is what is used to execute the
        LII application and redirects the output.

        @param cmd: The command to be executed
        @type cmd: string
        
        @param step: The amount to increment the progress bar at each step
        @type step: int
        """
        wx.Process.__init__(self, notify)
        self.Redirect()
        
        self._cmd = cmd
        logger.debug('Command given: '+self._cmd)

        self._observers = []

        self._pid = None

        self._progress = _ProgressDialog(None, wx.ID_ANY, 'Progress', self)
        
        self._count = 0

        self._step = step

        self._iscanceled = False

        self._has_error = False

        self._timer = wx.Timer(self)

        self.Bind(wx.EVT_TIMER, self._OnTimer)


    def Start(self):
        """
        Starts the LII application
        """
        logger.debug('Process starting')
        logger.debug('Executing: %s', self._cmd)
        
        self._pid = wx.Execute(self._cmd, wx.EXEC_ASYNC, self)
        
        # Resets flags
        self._iscanceled = False
        self._has_error = False

        # Starts the timer and notifies every 100 miliseconds
        self._timer.Start(100)
        self._progress.Show()
        logger.debug('Progress bar displayed')


    def _OnTimer(self, evt):
        """
        Dumps the output that has been loaded into the buffer
        whenever a certain amount of time has passed. 
        """
        logger.debug('Starting timer event')
        stream = self.GetInputStream()

        # Reads the input stream
        if stream.CanRead():
            logger.debug('Stream is readable')
            text = stream.read()
            
            self.NotifyAll(text)

            # Updates the progress bar
            if 'Current time' in text:
                logger.debug('Updating progress')
                self._count += self._step
                self._progress.SetValue(self._step)
    

    def OnTerminate(self, *args, **kwargs):
        """
        Cleans up the process
        """
        logger.debug('Entering OnTerminate')
        # Reads the last buffer
        stream = self.GetInputStream()
        if stream.CanRead():
            logger.debug('Reading contents of last buffer')
            text = stream.read()
            
            # Redirects the contents of the last buffer
            self.NotifyAll(text)

        if self._progress:
            logger.debug('Hiding progress bar')
            self._progress.Hide()
            self._count = 0
            self._progress.Reset()

        # Stops the timer
        if self._timer:
            self._timer.Stop()

        # Prints an appropriate end message
        if self._iscanceled:
            exit_message = '\n\nSimulation canceled.\n'
            self.NotifyAll(exit_message)
        elif self._has_error:
            exit_message = '\n\nThe simulation has encountered an error!\n'
            self.NotifyAll(exit_message)
        else:
            exit_message = '\n\nSimulation completed successfully!\n'
            self.NotifyAll(exit_message)

    def Register(self, o):
        """
        Adds an observer to the list of outputs.

        @param o: The observer to add to the list of outputs
        @type o: Observer
        """
        self._observers.append(o)

    def SetCmd(self, command):
        """
        Changes the value of cmd to command

        @param command: The new command to be run
        @type command: string
        """
        self._cmd = command


    def GetCmd(self):
        """
        Returns the value of cmd

        @return: The value of cmd
        @rtype: string
        """
        return self._cmd


    def GetPid(self):
        """
        Returns the process id of the application

        @return: The pid of the LII application
        @rtype: int
        """
        return self._pid


    def SetCount(self, count):
        """
        Sets the current count to the new count value

        @param count: The new count
        @type count: int
        """
        self._count = count


    def GetCount(self):
        """
        Returns the current count of the process.

        @return: The current value of the progress bar
        @rtype: int
        """
        return self._count


    def SetStep(self, step):
        """
        Sets the value of step as the object's step value

        @param step: The new value of step
        @type step: int
        """
        self._step = step


    def GetStep(self):
        """
        Returns the value of step

        @return: The value of step
        @rtype: int
        """
        return self._step


    def GetTimer(self):
        """
        Returns the timer

        @return: The timer of the process
        @rtype: timer
        """
        return self._timer

    def SetError(self, value):
        """
        Sets the value for self._has_error

        @type value: bool
        @param value: True if the output contains an error, False otherwise
        """
        self._has_error = True

    def Terminate(self):
        """
        Notifies the process that a signal to end
        the running process has been sent. Sets the
        self._iscanceled value to True.
        """
        self._timer.Stop()
        self._iscanceled = True
        wx.Kill(self._pid, wx.SIGKILL)

    def NotifyAll(self, text):
        """
        Sends the current output buffer dump to all observers

        @param text: The current output buffer contents
        @type text: string
        """
        for o in self._observers:
            o.Notify(text)

class _ProgressDialog(wx.Dialog):
    """
    This class creates a progress dialog to notify the user
    of the relative time remaining

    @type _process: Process
    @ivar _process: The running process that this class displays progress of

    @type _gauge: wx.Gauge
    @ivar _gauge: The bar that shows the overall progress
    """
    def __init__(self, parent, id, title, process, text=''):
        """
        Creates a dialog that displays the current progress
        of the running application.

        @param parent: The parent of the dialog
        @type parent: wx.Frame
        
        @param id: The id of the dialog window
        @type id: int
        
        @param title: The name of the progress dialog
        @type title: string
        
        @param process: A reference to the process that created this
        @type process: Process
        """
        logger.debug('Progress bar created')
        # Creates a dialog to display the frame and make things modal
        wx.Dialog.__init__(self, parent, id, title, size=(200,125), \
            style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)

        #: The process object that created this dialog object
        self._process = process

        display_text = wx.StaticText(self, wx.ID_ANY, text)
        
        #: The gauge that displays the current progress
        self._gauge = wx.Gauge(self, wx.ID_ANY, range=1000)
        
        #: Creates a button that allows the user to cancel the process
        closebutton = wx.Button(self, wx.ID_CANCEL)
        closebutton.Bind(wx.EVT_BUTTON, self._OnCancel)

        #: Packs the items into a container
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(display_text, 0 , wx.EXPAND)
        sizer.Add(self._gauge, 0, wx.ALIGN_CENTER)
        sizer.Add(closebutton, 0, wx.ALIGN_CENTER)

        self.SetSizer(sizer)


    def _OnCancel(self, event):
        """
        Cancels the process if the user clicks the cancel button
        """
        self._process.Terminate()


    def SetValue(self, value):
        """
        Updates the progress bar by adding value to
        the current value. If the value exceeds 1000
        then the bar is set to 1000.

        @param value: The amount to be added to the progress bar
        @type value: int
        """
        # Prevents the bar from exceeding its limit
        if (self._gauge.GetValue() + value) < 1000:
            self._gauge.SetValue(self._gauge.GetValue() + value)
            logger.debug('New progress value: %i', self._gauge.GetValue())
        else:
            self._gauge.SetValue(1000)

    def Reset(self):
        """
        Resets the progress bar so that it can be used later
        """
        logger.debug('Progress bar reset')
        self._gauge.SetValue(0)
